// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/service_worker/service_worker_database.h"

#include <stddef.h>
#include <stdint.h>

#include <string>

#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "content/browser/service_worker/service_worker_database.pb.h"
#include "content/common/service_worker/service_worker_types.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/leveldatabase/src/include/leveldb/write_batch.h"
#include "url/origin.h"

namespace content {

namespace {

    typedef ServiceWorkerDatabase::RegistrationData RegistrationData;
    typedef ServiceWorkerDatabase::ResourceRecord Resource;

    struct AvailableIds {
        int64_t reg_id;
        int64_t res_id;
        int64_t ver_id;

        AvailableIds()
            : reg_id(-1)
            , res_id(-1)
            , ver_id(-1)
        {
        }
        ~AvailableIds() { }
    };

    GURL URL(const GURL& origin, const std::string& path)
    {
        EXPECT_TRUE(origin.is_valid());
        EXPECT_EQ(origin, origin.GetOrigin());
        GURL out(origin.spec() + path);
        EXPECT_TRUE(out.is_valid());
        return out;
    }

    Resource CreateResource(int64_t resource_id,
        const GURL& url,
        uint64_t size_bytes)
    {
        EXPECT_TRUE(url.is_valid());
        return Resource(resource_id, url, size_bytes);
    }

    ServiceWorkerDatabase* CreateDatabase(const base::FilePath& path)
    {
        return new ServiceWorkerDatabase(path);
    }

    ServiceWorkerDatabase* CreateDatabaseInMemory()
    {
        return new ServiceWorkerDatabase(base::FilePath());
    }

    void VerifyRegistrationData(const RegistrationData& expected,
        const RegistrationData& actual)
    {
        EXPECT_EQ(expected.registration_id, actual.registration_id);
        EXPECT_EQ(expected.scope, actual.scope);
        EXPECT_EQ(expected.script, actual.script);
        EXPECT_EQ(expected.version_id, actual.version_id);
        EXPECT_EQ(expected.is_active, actual.is_active);
        EXPECT_EQ(expected.has_fetch_handler, actual.has_fetch_handler);
        EXPECT_EQ(expected.last_update_check, actual.last_update_check);
        EXPECT_EQ(expected.resources_total_size_bytes,
            actual.resources_total_size_bytes);
        EXPECT_EQ(expected.foreign_fetch_scopes, actual.foreign_fetch_scopes);
        EXPECT_EQ(expected.foreign_fetch_origins, actual.foreign_fetch_origins);
    }

    void VerifyResourceRecords(const std::vector<Resource>& expected,
        const std::vector<Resource>& actual)
    {
        ASSERT_EQ(expected.size(), actual.size());
        for (size_t i = 0; i < expected.size(); ++i) {
            EXPECT_EQ(expected[i].resource_id, actual[i].resource_id);
            EXPECT_EQ(expected[i].url, actual[i].url);
            EXPECT_EQ(expected[i].size_bytes, actual[i].size_bytes);
        }
    }

} // namespace

TEST(ServiceWorkerDatabaseTest, OpenDatabase)
{
    base::ScopedTempDir database_dir;
    ASSERT_TRUE(database_dir.CreateUniqueTempDir());
    std::unique_ptr<ServiceWorkerDatabase> database(
        CreateDatabase(database_dir.GetPath()));

    // Should be false because the database does not exist at the path.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->LazyOpen(false));

    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->LazyOpen(true));

    database.reset(CreateDatabase(database_dir.GetPath()));
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->LazyOpen(false));
}

TEST(ServiceWorkerDatabaseTest, OpenDatabase_InMemory)
{
    std::unique_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory());

    // Should be false because the database does not exist in memory.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->LazyOpen(false));

    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->LazyOpen(true));
    database.reset(CreateDatabaseInMemory());

    // Should be false because the database is not persistent.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->LazyOpen(false));
}

TEST(ServiceWorkerDatabaseTest, DatabaseVersion_ValidSchemaVersion)
{
    GURL origin("http://example.com");
    std::unique_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory());
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->LazyOpen(true));

    // Opening a new database does not write anything, so its schema version
    // should be 0.
    int64_t db_version = -1;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->ReadDatabaseVersion(&db_version));
    EXPECT_EQ(0u, db_version);

    // First writing triggers database initialization and bumps the schema
    // version.
    std::vector<ServiceWorkerDatabase::ResourceRecord> resources;
    resources.push_back(CreateResource(1, URL(origin, "/resource"), 10));
    ServiceWorkerDatabase::RegistrationData deleted_version;
    std::vector<int64_t> newly_purgeable_resources;
    ServiceWorkerDatabase::RegistrationData data;
    data.resources_total_size_bytes = 10;
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(
            data, resources, &deleted_version, &newly_purgeable_resources));

    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->ReadDatabaseVersion(&db_version));
    EXPECT_LT(0, db_version);
}

TEST(ServiceWorkerDatabaseTest, DatabaseVersion_ObsoleteSchemaVersion)
{
    base::ScopedTempDir database_dir;
    ASSERT_TRUE(database_dir.CreateUniqueTempDir());
    std::unique_ptr<ServiceWorkerDatabase> database(
        CreateDatabase(database_dir.GetPath()));
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->LazyOpen(true));

    // First writing triggers database initialization and bumps the schema
    // version.
    GURL origin("http://example.com");
    std::vector<ServiceWorkerDatabase::ResourceRecord> resources;
    resources.push_back(CreateResource(1, URL(origin, "/resource"), 10));
    ServiceWorkerDatabase::RegistrationData deleted_version;
    std::vector<int64_t> newly_purgeable_resources;
    ServiceWorkerDatabase::RegistrationData data;
    data.resources_total_size_bytes = 10;
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data, resources, &deleted_version,
            &newly_purgeable_resources));
    int64_t db_version = -1;
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->ReadDatabaseVersion(&db_version));
    ASSERT_LT(0, db_version);

    // Emulate an obsolete schema version.
    int64_t old_db_version = 1;
    leveldb::WriteBatch batch;
    batch.Put("INITDATA_DB_VERSION", base::Int64ToString(old_db_version));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, database->WriteBatch(&batch));
    db_version = -1;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->ReadDatabaseVersion(&db_version));
    ASSERT_EQ(old_db_version, db_version);

    // Opening the database whose schema version is obsolete should fail.
    database.reset(CreateDatabase(database_dir.GetPath()));
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_FAILED,
        database->LazyOpen(true));
}

TEST(ServiceWorkerDatabaseTest, DatabaseVersion_CorruptedSchemaVersion)
{
    base::ScopedTempDir database_dir;
    ASSERT_TRUE(database_dir.CreateUniqueTempDir());
    std::unique_ptr<ServiceWorkerDatabase> database(
        CreateDatabase(database_dir.GetPath()));
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->LazyOpen(true));

    // First writing triggers database initialization and bumps the schema
    // version.
    GURL origin("http://example.com");
    std::vector<ServiceWorkerDatabase::ResourceRecord> resources;
    resources.push_back(CreateResource(1, URL(origin, "/resource"), 10));
    ServiceWorkerDatabase::RegistrationData deleted_version;
    std::vector<int64_t> newly_purgeable_resources;
    ServiceWorkerDatabase::RegistrationData data;
    data.resources_total_size_bytes = 10;
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data, resources, &deleted_version,
            &newly_purgeable_resources));
    int64_t db_version = -1;
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->ReadDatabaseVersion(&db_version));
    ASSERT_LT(0, db_version);

    // Emulate a corrupted schema version.
    int64_t corrupted_db_version = -10;
    leveldb::WriteBatch batch;
    batch.Put("INITDATA_DB_VERSION", base::Int64ToString(corrupted_db_version));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, database->WriteBatch(&batch));
    db_version = -1;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED,
        database->ReadDatabaseVersion(&db_version));

    // Opening the database whose schema version is corrupted should fail.
    database.reset(CreateDatabase(database_dir.GetPath()));
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED,
        database->LazyOpen(true));
}

TEST(ServiceWorkerDatabaseTest, GetNextAvailableIds)
{
    base::ScopedTempDir database_dir;
    ASSERT_TRUE(database_dir.CreateUniqueTempDir());
    std::unique_ptr<ServiceWorkerDatabase> database(
        CreateDatabase(database_dir.GetPath()));

    GURL origin("http://example.com");

    // The database has never been used, so returns initial values.
    AvailableIds ids;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->GetNextAvailableIds(&ids.reg_id, &ids.ver_id, &ids.res_id));
    EXPECT_EQ(0, ids.reg_id);
    EXPECT_EQ(0, ids.ver_id);
    EXPECT_EQ(0, ids.res_id);

    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK, database->LazyOpen(true));
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->GetNextAvailableIds(&ids.reg_id, &ids.ver_id, &ids.res_id));
    EXPECT_EQ(0, ids.reg_id);
    EXPECT_EQ(0, ids.ver_id);
    EXPECT_EQ(0, ids.res_id);

    // Writing uncommitted resources bumps the next available resource id.
    const int64_t kUncommittedIds[] = { 0, 1, 3, 5, 6, 10 };
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->WriteUncommittedResourceIds(std::set<int64_t>(
            kUncommittedIds, kUncommittedIds + arraysize(kUncommittedIds))));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->GetNextAvailableIds(&ids.reg_id, &ids.ver_id, &ids.res_id));
    EXPECT_EQ(0, ids.reg_id);
    EXPECT_EQ(0, ids.ver_id);
    EXPECT_EQ(11, ids.res_id);

    // Writing purgeable resources bumps the next available id.
    const int64_t kPurgeableIds[] = { 4, 12, 16, 17, 20 };
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteUncommittedResourceIds(std::set<int64_t>(
            kPurgeableIds, kPurgeableIds + arraysize(kPurgeableIds))));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->GetNextAvailableIds(&ids.reg_id, &ids.ver_id, &ids.res_id));
    EXPECT_EQ(0, ids.reg_id);
    EXPECT_EQ(0, ids.ver_id);
    EXPECT_EQ(21, ids.res_id);

    // Writing a registration bumps the next available registration and version
    // ids.
    std::vector<Resource> resources1;
    RegistrationData data1;
    ServiceWorkerDatabase::RegistrationData deleted_version;
    std::vector<int64_t> newly_purgeable_resources;
    data1.registration_id = 100;
    data1.scope = URL(origin, "/foo");
    data1.script = URL(origin, "/script1.js");
    data1.version_id = 200;
    data1.resources_total_size_bytes = 300;
    resources1.push_back(CreateResource(1, data1.script, 300));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data1, resources1, &deleted_version,
            &newly_purgeable_resources));

    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->GetNextAvailableIds(&ids.reg_id, &ids.ver_id, &ids.res_id));
    EXPECT_EQ(101, ids.reg_id);
    EXPECT_EQ(201, ids.ver_id);
    EXPECT_EQ(21, ids.res_id);

    // Writing a registration whose ids are lower than the stored ones should not
    // bump the next available ids.
    RegistrationData data2;
    data2.registration_id = 10;
    data2.scope = URL(origin, "/bar");
    data2.script = URL(origin, "/script2.js");
    data2.version_id = 20;
    data2.resources_total_size_bytes = 400;
    std::vector<Resource> resources2;
    resources2.push_back(CreateResource(2, data2.script, 400));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data2, resources2, &deleted_version,
            &newly_purgeable_resources));

    // Same with resources.
    int64_t kLowResourceId = 15;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteUncommittedResourceIds(
            std::set<int64_t>(&kLowResourceId, &kLowResourceId + 1)));

    // Close and reopen the database to verify the stored values.
    database.reset(CreateDatabase(database_dir.GetPath()));

    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->GetNextAvailableIds(&ids.reg_id, &ids.ver_id, &ids.res_id));
    EXPECT_EQ(101, ids.reg_id);
    EXPECT_EQ(201, ids.ver_id);
    EXPECT_EQ(21, ids.res_id);
}

TEST(ServiceWorkerDatabaseTest, GetOriginsWithRegistrations)
{
    std::unique_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory());

    std::set<GURL> origins;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetOriginsWithRegistrations(&origins));
    EXPECT_TRUE(origins.empty());

    ServiceWorkerDatabase::RegistrationData deleted_version;
    std::vector<int64_t> newly_purgeable_resources;

    GURL origin1("http://example.com");
    RegistrationData data1;
    data1.registration_id = 123;
    data1.scope = URL(origin1, "/foo");
    data1.script = URL(origin1, "/script1.js");
    data1.version_id = 456;
    data1.resources_total_size_bytes = 100;
    std::vector<Resource> resources1;
    resources1.push_back(CreateResource(1, data1.script, 100));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data1, resources1, &deleted_version,
            &newly_purgeable_resources));

    GURL origin2("https://www.example.com");
    RegistrationData data2;
    data2.registration_id = 234;
    data2.scope = URL(origin2, "/bar");
    data2.script = URL(origin2, "/script2.js");
    data2.version_id = 567;
    data2.resources_total_size_bytes = 200;
    std::vector<Resource> resources2;
    resources2.push_back(CreateResource(2, data2.script, 200));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data2, resources2, &deleted_version,
            &newly_purgeable_resources));

    GURL origin3("https://example.org");
    RegistrationData data3;
    data3.registration_id = 345;
    data3.scope = URL(origin3, "/hoge");
    data3.script = URL(origin3, "/script3.js");
    data3.version_id = 678;
    data3.resources_total_size_bytes = 300;
    std::vector<Resource> resources3;
    resources3.push_back(CreateResource(3, data3.script, 300));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data3, resources3, &deleted_version,
            &newly_purgeable_resources));

    // |origin3| has two registrations.
    RegistrationData data4;
    data4.registration_id = 456;
    data4.scope = URL(origin3, "/fuga");
    data4.script = URL(origin3, "/script4.js");
    data4.version_id = 789;
    data4.resources_total_size_bytes = 400;
    std::vector<Resource> resources4;
    resources4.push_back(CreateResource(4, data4.script, 400));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data4, resources4, &deleted_version,
            &newly_purgeable_resources));

    origins.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetOriginsWithRegistrations(&origins));
    EXPECT_EQ(3U, origins.size());
    EXPECT_TRUE(base::ContainsKey(origins, origin1));
    EXPECT_TRUE(base::ContainsKey(origins, origin2));
    EXPECT_TRUE(base::ContainsKey(origins, origin3));

    // |origin3| has another registration, so should not remove it from the
    // unique origin list.
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->DeleteRegistration(data4.registration_id,
            origin3,
            &deleted_version,
            &newly_purgeable_resources));
    EXPECT_EQ(data4.registration_id, deleted_version.registration_id);

    origins.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetOriginsWithRegistrations(&origins));
    EXPECT_EQ(3U, origins.size());
    EXPECT_TRUE(base::ContainsKey(origins, origin1));
    EXPECT_TRUE(base::ContainsKey(origins, origin2));
    EXPECT_TRUE(base::ContainsKey(origins, origin3));

    // |origin3| should be removed from the unique origin list.
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->DeleteRegistration(data3.registration_id,
            origin3,
            &deleted_version,
            &newly_purgeable_resources));
    EXPECT_EQ(data3.registration_id, deleted_version.registration_id);

    origins.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetOriginsWithRegistrations(&origins));
    EXPECT_EQ(2U, origins.size());
    EXPECT_TRUE(base::ContainsKey(origins, origin1));
    EXPECT_TRUE(base::ContainsKey(origins, origin2));
}

TEST(ServiceWorkerDatabaseTest, GetRegistrationsForOrigin)
{
    std::unique_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory());

    GURL origin1("http://example.com");
    GURL origin2("https://www.example.com");
    GURL origin3("https://example.org");

    std::vector<RegistrationData> registrations;
    std::vector<std::vector<Resource>> resources_list;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetRegistrationsForOrigin(origin1, &registrations,
            &resources_list));
    EXPECT_TRUE(registrations.empty());
    EXPECT_TRUE(resources_list.empty());

    ServiceWorkerDatabase::RegistrationData deleted_version;
    std::vector<int64_t> newly_purgeable_resources;

    RegistrationData data1;
    data1.registration_id = 100;
    data1.scope = URL(origin1, "/foo");
    data1.script = URL(origin1, "/script1.js");
    data1.version_id = 1000;
    data1.resources_total_size_bytes = 100;
    std::vector<Resource> resources1;
    resources1.push_back(CreateResource(1, data1.script, 100));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data1, resources1, &deleted_version,
            &newly_purgeable_resources));

    registrations.clear();
    resources_list.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetRegistrationsForOrigin(origin1, &registrations,
            &resources_list));
    EXPECT_EQ(1U, registrations.size());
    VerifyRegistrationData(data1, registrations[0]);
    EXPECT_EQ(1U, resources_list.size());
    VerifyResourceRecords(resources1, resources_list[0]);

    RegistrationData data2;
    data2.registration_id = 200;
    data2.scope = URL(origin2, "/bar");
    data2.script = URL(origin2, "/script2.js");
    data2.version_id = 2000;
    data2.resources_total_size_bytes = 200;
    std::vector<Resource> resources2;
    resources2.push_back(CreateResource(2, data2.script, 200));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data2, resources2, &deleted_version,
            &newly_purgeable_resources));

    registrations.clear();
    resources_list.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetRegistrationsForOrigin(origin2, &registrations,
            &resources_list));
    EXPECT_EQ(1U, registrations.size());
    VerifyRegistrationData(data2, registrations[0]);
    EXPECT_EQ(1U, resources_list.size());
    VerifyResourceRecords(resources2, resources_list[0]);

    RegistrationData data3;
    data3.registration_id = 300;
    data3.scope = URL(origin3, "/hoge");
    data3.script = URL(origin3, "/script3.js");
    data3.version_id = 3000;
    data3.resources_total_size_bytes = 300;
    std::vector<Resource> resources3;
    resources3.push_back(CreateResource(3, data3.script, 300));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data3, resources3, &deleted_version,
            &newly_purgeable_resources));

    // |origin3| has two registrations.
    RegistrationData data4;
    data4.registration_id = 400;
    data4.scope = URL(origin3, "/fuga");
    data4.script = URL(origin3, "/script4.js");
    data4.version_id = 4000;
    data4.resources_total_size_bytes = 400;
    std::vector<Resource> resources4;
    resources4.push_back(CreateResource(4, data4.script, 400));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data4, resources4, &deleted_version,
            &newly_purgeable_resources));

    registrations.clear();
    resources_list.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetRegistrationsForOrigin(origin3, &registrations,
            &resources_list));
    EXPECT_EQ(2U, registrations.size());
    VerifyRegistrationData(data3, registrations[0]);
    VerifyRegistrationData(data4, registrations[1]);
    EXPECT_EQ(2U, resources_list.size());
    VerifyResourceRecords(resources3, resources_list[0]);
    VerifyResourceRecords(resources4, resources_list[1]);

    // The third parameter |opt_resources_list| to GetRegistrationsForOrigin()
    // is optional. So, nullptr should be acceptable.
    registrations.clear();
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->GetRegistrationsForOrigin(origin1, &registrations, nullptr));
    EXPECT_EQ(1U, registrations.size());
    VerifyRegistrationData(data1, registrations[0]);
}

TEST(ServiceWorkerDatabaseTest, GetAllRegistrations)
{
    std::unique_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory());

    std::vector<RegistrationData> registrations;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetAllRegistrations(&registrations));
    EXPECT_TRUE(registrations.empty());

    ServiceWorkerDatabase::RegistrationData deleted_version;
    std::vector<int64_t> newly_purgeable_resources;

    GURL origin1("http://www1.example.com");
    RegistrationData data1;
    data1.registration_id = 100;
    data1.scope = URL(origin1, "/foo");
    data1.script = URL(origin1, "/script1.js");
    data1.version_id = 1000;
    data1.resources_total_size_bytes = 100;
    std::vector<Resource> resources1;
    resources1.push_back(CreateResource(1, data1.script, 100));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data1, resources1, &deleted_version,
            &newly_purgeable_resources));

    GURL origin2("http://www2.example.com");
    RegistrationData data2;
    data2.registration_id = 200;
    data2.scope = URL(origin2, "/bar");
    data2.script = URL(origin2, "/script2.js");
    data2.version_id = 2000;
    data2.resources_total_size_bytes = 200;
    std::vector<Resource> resources2;
    resources2.push_back(CreateResource(2, data2.script, 200));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data2, resources2, &deleted_version,
            &newly_purgeable_resources));

    GURL origin3("http://www3.example.com");
    RegistrationData data3;
    data3.registration_id = 300;
    data3.scope = URL(origin3, "/hoge");
    data3.script = URL(origin3, "/script3.js");
    data3.version_id = 3000;
    data3.resources_total_size_bytes = 300;
    std::vector<Resource> resources3;
    resources3.push_back(CreateResource(3, data3.script, 300));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data3, resources3, &deleted_version,
            &newly_purgeable_resources));

    // |origin3| has two registrations.
    RegistrationData data4;
    data4.registration_id = 400;
    data4.scope = URL(origin3, "/fuga");
    data4.script = URL(origin3, "/script4.js");
    data4.version_id = 4000;
    data4.resources_total_size_bytes = 400;
    std::vector<Resource> resources4;
    resources4.push_back(CreateResource(4, data4.script, 400));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data4, resources4, &deleted_version,
            &newly_purgeable_resources));

    registrations.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetAllRegistrations(&registrations));
    EXPECT_EQ(4U, registrations.size());
    VerifyRegistrationData(data1, registrations[0]);
    VerifyRegistrationData(data2, registrations[1]);
    VerifyRegistrationData(data3, registrations[2]);
    VerifyRegistrationData(data4, registrations[3]);
}

TEST(ServiceWorkerDatabaseTest, Registration_Basic)
{
    std::unique_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory());

    GURL origin("http://example.com");
    RegistrationData data;
    data.registration_id = 100;
    data.scope = URL(origin, "/foo");
    data.script = URL(origin, "/resource1");
    data.version_id = 200;
    data.resources_total_size_bytes = 10939 + 200;
    data.foreign_fetch_scopes.push_back(URL(origin, "/foo/bar"));

    std::vector<Resource> resources;
    resources.push_back(CreateResource(1, URL(origin, "/resource1"), 10939));
    resources.push_back(CreateResource(2, URL(origin, "/resource2"), 200));

    // Write a resource to the uncommitted list to make sure that writing
    // registration removes resource ids associated with the registration from
    // the uncommitted list.
    std::set<int64_t> uncommitted_ids;
    uncommitted_ids.insert(resources[0].resource_id);
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteUncommittedResourceIds(uncommitted_ids));
    std::set<int64_t> uncommitted_ids_out;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetUncommittedResourceIds(&uncommitted_ids_out));
    EXPECT_EQ(uncommitted_ids, uncommitted_ids_out);

    ServiceWorkerDatabase::RegistrationData deleted_version;
    deleted_version.version_id = 222; // Dummy initial value
    std::vector<int64_t> newly_purgeable_resources;

    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(
            data, resources, &deleted_version, &newly_purgeable_resources));
    EXPECT_EQ(kInvalidServiceWorkerVersionId, deleted_version.version_id);
    EXPECT_TRUE(newly_purgeable_resources.empty());

    // Make sure that the registration and resource records are stored.
    RegistrationData data_out;
    std::vector<Resource> resources_out;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->ReadRegistration(
            data.registration_id, origin, &data_out, &resources_out));
    VerifyRegistrationData(data, data_out);
    VerifyResourceRecords(resources, resources_out);
    GURL origin_out;
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadRegistrationOrigin(data.registration_id, &origin_out));
    EXPECT_EQ(origin, origin_out);

    // Make sure that the resource is removed from the uncommitted list.
    uncommitted_ids_out.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetUncommittedResourceIds(&uncommitted_ids_out));
    EXPECT_TRUE(uncommitted_ids_out.empty());

    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->DeleteRegistration(data.registration_id,
            origin,
            &deleted_version,
            &newly_purgeable_resources));
    EXPECT_EQ(data.version_id, deleted_version.version_id);
    ASSERT_EQ(resources.size(), newly_purgeable_resources.size());
    for (size_t i = 0; i < resources.size(); ++i)
        EXPECT_EQ(newly_purgeable_resources[i], resources[i].resource_id);

    // Make sure that the registration and resource records are gone.
    resources_out.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadRegistration(
            data.registration_id, origin, &data_out, &resources_out));
    EXPECT_TRUE(resources_out.empty());
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadRegistrationOrigin(data.registration_id, &origin_out));

    // Resources should be purgeable because these are no longer referred.
    std::set<int64_t> purgeable_ids_out;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetPurgeableResourceIds(&purgeable_ids_out));
    EXPECT_EQ(2u, purgeable_ids_out.size());
    EXPECT_TRUE(base::ContainsKey(purgeable_ids_out, resources[0].resource_id));
    EXPECT_TRUE(base::ContainsKey(purgeable_ids_out, resources[1].resource_id));
}

TEST(ServiceWorkerDatabaseTest, DeleteNonExistentRegistration)
{
    std::unique_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory());

    GURL origin("http://example.com");
    RegistrationData data;
    data.registration_id = 100;
    data.scope = URL(origin, "/foo");
    data.script = URL(origin, "/resource1");
    data.version_id = 200;
    data.resources_total_size_bytes = 19 + 29129;

    std::vector<Resource> resources;
    resources.push_back(CreateResource(1, URL(origin, "/resource1"), 19));
    resources.push_back(CreateResource(2, URL(origin, "/resource2"), 29129));

    const int64_t kNonExistentRegistrationId = 999;
    const int64_t kArbitraryVersionId = 222; // Used as a dummy initial value

    ServiceWorkerDatabase::RegistrationData deleted_version;
    deleted_version.version_id = kArbitraryVersionId;
    std::vector<int64_t> newly_purgeable_resources;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(
            data, resources, &deleted_version, &newly_purgeable_resources));
    EXPECT_EQ(kInvalidServiceWorkerVersionId, deleted_version.version_id);
    EXPECT_TRUE(newly_purgeable_resources.empty());

    // Delete from an origin that has a registration.
    deleted_version.version_id = kArbitraryVersionId;
    newly_purgeable_resources.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->DeleteRegistration(kNonExistentRegistrationId,
            origin,
            &deleted_version,
            &newly_purgeable_resources));
    EXPECT_EQ(kInvalidServiceWorkerVersionId, deleted_version.version_id);
    EXPECT_TRUE(newly_purgeable_resources.empty());

    // Delete from an origin that has no registration.
    deleted_version.version_id = kArbitraryVersionId;
    newly_purgeable_resources.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->DeleteRegistration(kNonExistentRegistrationId,
            GURL("http://example.net"),
            &deleted_version,
            &newly_purgeable_resources));
    EXPECT_EQ(kInvalidServiceWorkerVersionId, deleted_version.version_id);
    EXPECT_TRUE(newly_purgeable_resources.empty());
}

TEST(ServiceWorkerDatabaseTest, Registration_Overwrite)
{
    std::unique_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory());

    GURL origin("http://example.com");
    RegistrationData data;
    data.registration_id = 100;
    data.scope = URL(origin, "/foo");
    data.script = URL(origin, "/resource1");
    data.version_id = 200;
    data.resources_total_size_bytes = 10 + 11;
    data.foreign_fetch_scopes.push_back(URL(origin, "/foo"));
    data.foreign_fetch_origins.push_back(
        url::Origin(GURL("https://chromium.org")));

    std::vector<Resource> resources1;
    resources1.push_back(CreateResource(1, URL(origin, "/resource1"), 10));
    resources1.push_back(CreateResource(2, URL(origin, "/resource2"), 11));

    ServiceWorkerDatabase::RegistrationData deleted_version;
    deleted_version.version_id = 222; // Dummy inital value
    std::vector<int64_t> newly_purgeable_resources;

    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(
            data, resources1, &deleted_version, &newly_purgeable_resources));
    EXPECT_EQ(kInvalidServiceWorkerVersionId, deleted_version.version_id);
    EXPECT_TRUE(newly_purgeable_resources.empty());

    // Make sure that the registration and resource records are stored.
    RegistrationData data_out;
    std::vector<Resource> resources_out;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->ReadRegistration(data.registration_id, origin, &data_out, &resources_out));
    VerifyRegistrationData(data, data_out);
    VerifyResourceRecords(resources1, resources_out);

    // Update the registration.
    RegistrationData updated_data = data;
    updated_data.script = URL(origin, "/resource3");
    updated_data.version_id = data.version_id + 1;
    updated_data.resources_total_size_bytes = 12 + 13;
    updated_data.foreign_fetch_scopes.clear();
    updated_data.foreign_fetch_origins.push_back(
        url::Origin(GURL("https://example.com")));
    std::vector<Resource> resources2;
    resources2.push_back(CreateResource(3, URL(origin, "/resource3"), 12));
    resources2.push_back(CreateResource(4, URL(origin, "/resource4"), 13));

    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(updated_data,
            resources2,
            &deleted_version,
            &newly_purgeable_resources));
    EXPECT_EQ(data.version_id, deleted_version.version_id);
    ASSERT_EQ(resources1.size(), newly_purgeable_resources.size());
    for (size_t i = 0; i < resources1.size(); ++i)
        EXPECT_EQ(newly_purgeable_resources[i], resources1[i].resource_id);

    // Make sure that |updated_data| is stored and resources referred from |data|
    // is moved to the purgeable list.
    resources_out.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->ReadRegistration(updated_data.registration_id, origin, &data_out, &resources_out));
    VerifyRegistrationData(updated_data, data_out);
    VerifyResourceRecords(resources2, resources_out);

    std::set<int64_t> purgeable_ids_out;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetPurgeableResourceIds(&purgeable_ids_out));
    EXPECT_EQ(2u, purgeable_ids_out.size());
    EXPECT_TRUE(base::ContainsKey(purgeable_ids_out, resources1[0].resource_id));
    EXPECT_TRUE(base::ContainsKey(purgeable_ids_out, resources1[1].resource_id));
}

TEST(ServiceWorkerDatabaseTest, Registration_Multiple)
{
    std::unique_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory());
    GURL origin("http://example.com");

    ServiceWorkerDatabase::RegistrationData deleted_version;
    std::vector<int64_t> newly_purgeable_resources;

    // Add registration1.
    RegistrationData data1;
    data1.registration_id = 100;
    data1.scope = URL(origin, "/foo");
    data1.script = URL(origin, "/resource1");
    data1.version_id = 200;
    data1.resources_total_size_bytes = 1451 + 15234;

    std::vector<Resource> resources1;
    resources1.push_back(CreateResource(1, URL(origin, "/resource1"), 1451));
    resources1.push_back(CreateResource(2, URL(origin, "/resource2"), 15234));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(
            data1, resources1, &deleted_version, &newly_purgeable_resources));

    // Add registration2.
    RegistrationData data2;
    data2.registration_id = 101;
    data2.scope = URL(origin, "/bar");
    data2.script = URL(origin, "/resource3");
    data2.version_id = 201;
    data2.resources_total_size_bytes = 5 + 6;

    std::vector<Resource> resources2;
    resources2.push_back(CreateResource(3, URL(origin, "/resource3"), 5));
    resources2.push_back(CreateResource(4, URL(origin, "/resource4"), 6));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(
            data2, resources2, &deleted_version, &newly_purgeable_resources));

    // Make sure that registration1 is stored.
    RegistrationData data_out;
    std::vector<Resource> resources_out;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->ReadRegistration(data1.registration_id, origin, &data_out, &resources_out));
    VerifyRegistrationData(data1, data_out);
    VerifyResourceRecords(resources1, resources_out);
    GURL origin_out;
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadRegistrationOrigin(data1.registration_id, &origin_out));
    EXPECT_EQ(origin, origin_out);

    // Make sure that registration2 is also stored.
    resources_out.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->ReadRegistration(data2.registration_id, origin, &data_out, &resources_out));
    VerifyRegistrationData(data2, data_out);
    VerifyResourceRecords(resources2, resources_out);
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadRegistrationOrigin(data2.registration_id, &origin_out));
    EXPECT_EQ(origin, origin_out);

    std::set<int64_t> purgeable_ids_out;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetPurgeableResourceIds(&purgeable_ids_out));
    EXPECT_TRUE(purgeable_ids_out.empty());

    // Delete registration1.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->DeleteRegistration(data1.registration_id,
            origin,
            &deleted_version,
            &newly_purgeable_resources));
    EXPECT_EQ(data1.registration_id, deleted_version.registration_id);

    // Make sure that registration1 is gone.
    resources_out.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadRegistration(
            data1.registration_id, origin, &data_out, &resources_out));
    EXPECT_TRUE(resources_out.empty());
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadRegistrationOrigin(data1.registration_id, &origin_out));

    purgeable_ids_out.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetPurgeableResourceIds(&purgeable_ids_out));
    EXPECT_EQ(2u, purgeable_ids_out.size());
    EXPECT_TRUE(base::ContainsKey(purgeable_ids_out, resources1[0].resource_id));
    EXPECT_TRUE(base::ContainsKey(purgeable_ids_out, resources1[1].resource_id));

    // Make sure that registration2 is still alive.
    resources_out.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->ReadRegistration(data2.registration_id, origin, &data_out, &resources_out));
    VerifyRegistrationData(data2, data_out);
    VerifyResourceRecords(resources2, resources_out);
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadRegistrationOrigin(data2.registration_id, &origin_out));
    EXPECT_EQ(origin, origin_out);
}

TEST(ServiceWorkerDatabaseTest, Registration_UninitializedDatabase)
{
    std::unique_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory());
    const GURL origin("http://example.com");

    // Should be failed because the database does not exist.
    RegistrationData data_out;
    std::vector<Resource> resources_out;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadRegistration(
            100, origin, &data_out, &resources_out));
    EXPECT_EQ(kInvalidServiceWorkerRegistrationId, data_out.registration_id);
    EXPECT_TRUE(resources_out.empty());
    GURL origin_out;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadRegistrationOrigin(100, &origin_out));

    // Deleting non-existent registration should succeed.
    RegistrationData deleted_version;
    std::vector<int64_t> newly_purgeable_resources;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->DeleteRegistration(
            100, origin, &deleted_version, &newly_purgeable_resources));
    EXPECT_EQ(kInvalidServiceWorkerVersionId, deleted_version.version_id);
    EXPECT_TRUE(newly_purgeable_resources.empty());

    // Actually create a new database, but not initialized yet.
    database->LazyOpen(true);

    // Should be failed because the database is not initialized.
    ASSERT_EQ(ServiceWorkerDatabase::UNINITIALIZED, database->state_);
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadRegistration(
            100, origin, &data_out, &resources_out));
    EXPECT_EQ(kInvalidServiceWorkerRegistrationId, data_out.registration_id);
    EXPECT_TRUE(resources_out.empty());
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadRegistrationOrigin(100, &origin_out));

    // Deleting non-existent registration should succeed.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->DeleteRegistration(
            100, origin, &deleted_version, &newly_purgeable_resources));
    EXPECT_EQ(kInvalidServiceWorkerVersionId, deleted_version.version_id);
    EXPECT_TRUE(newly_purgeable_resources.empty());
}

TEST(ServiceWorkerDatabaseTest, UserData_Basic)
{
    std::unique_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory());
    const GURL kOrigin("http://example.com");

    // Add a registration.
    RegistrationData data;
    data.registration_id = 100;
    data.scope = URL(kOrigin, "/foo");
    data.script = URL(kOrigin, "/script.js");
    data.version_id = 200;
    data.resources_total_size_bytes = 100;
    std::vector<Resource> resources;
    resources.push_back(CreateResource(1, data.script, 100));
    ServiceWorkerDatabase::RegistrationData deleted_version;
    std::vector<int64_t> newly_purgeable_resources;
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(
            data, resources, &deleted_version, &newly_purgeable_resources));

    // Write user data associated with the stored registration.
    std::vector<std::string> user_data_out;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteUserData(data.registration_id, kOrigin,
            { { "key1", "data" } }));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadUserData(data.registration_id, { "key1" }, &user_data_out));
    ASSERT_EQ(1u, user_data_out.size());
    EXPECT_EQ("data", user_data_out[0]);

    // Writing user data not associated with the stored registration should be
    // failed.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->WriteUserData(300, kOrigin, { { "key1", "data" } }));

    // Write empty user data for a different key.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteUserData(data.registration_id, kOrigin,
            { { "key2", std::string() } }));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadUserData(data.registration_id, { "key2" }, &user_data_out));
    ASSERT_EQ(1u, user_data_out.size());
    EXPECT_EQ(std::string(), user_data_out[0]);
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadUserData(data.registration_id, { "key1" }, &user_data_out));
    ASSERT_EQ(1u, user_data_out.size());
    EXPECT_EQ("data", user_data_out[0]);

    // Overwrite the existing user data.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteUserData(data.registration_id, kOrigin,
            { { "key1", "overwrite" } }));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadUserData(data.registration_id, { "key1" }, &user_data_out));
    ASSERT_EQ(1u, user_data_out.size());
    EXPECT_EQ("overwrite", user_data_out[0]);

    // Delete the user data.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->DeleteUserData(data.registration_id, { "key1" }));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadUserData(data.registration_id, { "key1" }, &user_data_out));
    EXPECT_TRUE(user_data_out.empty());
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadUserData(data.registration_id, { "key2" }, &user_data_out));
    ASSERT_EQ(1u, user_data_out.size());
    EXPECT_EQ(std::string(), user_data_out[0]);

    // Write/overwrite multiple user data keys.
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->WriteUserData(
            data.registration_id, kOrigin,
            { { "key2", "overwrite2" }, { "key3", "data3" }, { "key4", "data4" } }));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadUserData(data.registration_id, { "key1" }, &user_data_out));
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->ReadUserData(data.registration_id,
            { "key2", "key3", "key4" }, &user_data_out));
    ASSERT_EQ(3u, user_data_out.size());
    EXPECT_EQ("overwrite2", user_data_out[0]);
    EXPECT_EQ("data3", user_data_out[1]);
    EXPECT_EQ("data4", user_data_out[2]);
    // Multiple reads fail if one is not found.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadUserData(data.registration_id, { "key2", "key1" },
            &user_data_out));
    EXPECT_TRUE(user_data_out.empty());

    // Delete multiple user data keys, even if some are not found.
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->DeleteUserData(data.registration_id, { "key1", "key2", "key3" }));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadUserData(data.registration_id, { "key1" }, &user_data_out));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadUserData(data.registration_id, { "key2" }, &user_data_out));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadUserData(data.registration_id, { "key3" }, &user_data_out));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadUserData(data.registration_id, { "key4" }, &user_data_out));
    ASSERT_EQ(1u, user_data_out.size());
    EXPECT_EQ("data4", user_data_out[0]);
}

TEST(ServiceWorkerDatabaseTest, UserData_DataIsolation)
{
    std::unique_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory());
    const GURL kOrigin("http://example.com");

    // Add registration 1.
    RegistrationData data1;
    data1.registration_id = 100;
    data1.scope = URL(kOrigin, "/foo");
    data1.script = URL(kOrigin, "/script1.js");
    data1.version_id = 200;
    data1.resources_total_size_bytes = 100;
    std::vector<Resource> resources1;
    resources1.push_back(CreateResource(1, data1.script, 100));

    // Add registration 2.
    RegistrationData data2;
    data2.registration_id = 101;
    data2.scope = URL(kOrigin, "/bar");
    data2.script = URL(kOrigin, "/script2.js");
    data2.version_id = 201;
    data2.resources_total_size_bytes = 200;
    std::vector<Resource> resources2;
    resources2.push_back(CreateResource(2, data2.script, 200));

    ServiceWorkerDatabase::RegistrationData deleted_version;
    std::vector<int64_t> newly_purgeable_resources;
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data1, resources1, &deleted_version,
            &newly_purgeable_resources));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data2, resources2, &deleted_version,
            &newly_purgeable_resources));

    // Write user data associated with the registration1.
    std::vector<std::string> user_data_out;
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteUserData(data1.registration_id, kOrigin,
            { { "key", "data1" } }));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadUserData(data1.registration_id, { "key" }, &user_data_out));
    ASSERT_EQ(1u, user_data_out.size());
    EXPECT_EQ("data1", user_data_out[0]);
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadUserData(data2.registration_id, { "key" }, &user_data_out));

    // Write user data associated with the registration2. This shouldn't overwrite
    // the data associated with registration1.
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteUserData(data2.registration_id, kOrigin,
            { { "key", "data2" } }));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadUserData(data1.registration_id, { "key" }, &user_data_out));
    ASSERT_EQ(1u, user_data_out.size());
    EXPECT_EQ("data1", user_data_out[0]);
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadUserData(data2.registration_id, { "key" }, &user_data_out));
    ASSERT_EQ(1u, user_data_out.size());
    EXPECT_EQ("data2", user_data_out[0]);

    // Get all registrations with user data.
    std::vector<std::pair<int64_t, std::string>> user_data_list;
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->ReadUserDataForAllRegistrations("key", &user_data_list));
    EXPECT_EQ(2u, user_data_list.size());
    EXPECT_EQ(data1.registration_id, user_data_list[0].first);
    EXPECT_EQ("data1", user_data_list[0].second);
    EXPECT_EQ(data2.registration_id, user_data_list[1].first);
    EXPECT_EQ("data2", user_data_list[1].second);

    // Delete the data associated with the registration2. This shouldn't delete
    // the data associated with registration1.
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->DeleteUserData(data2.registration_id, { "key" }));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadUserData(data1.registration_id, { "key" }, &user_data_out));
    ASSERT_EQ(1u, user_data_out.size());
    EXPECT_EQ("data1", user_data_out[0]);
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadUserData(data2.registration_id, { "key" }, &user_data_out));

    // And again get all registrations with user data.
    user_data_list.clear();
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->ReadUserDataForAllRegistrations("key", &user_data_list));
    EXPECT_EQ(1u, user_data_list.size());
    EXPECT_EQ(data1.registration_id, user_data_list[0].first);
    EXPECT_EQ("data1", user_data_list[0].second);
}

TEST(ServiceWorkerDatabaseTest, UserData_DeleteRegistration)
{
    std::unique_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory());
    const GURL kOrigin("http://example.com");

    // Add registration 1.
    RegistrationData data1;
    data1.registration_id = 100;
    data1.scope = URL(kOrigin, "/foo");
    data1.script = URL(kOrigin, "/script1.js");
    data1.version_id = 200;
    data1.resources_total_size_bytes = 100;
    std::vector<Resource> resources1;
    resources1.push_back(CreateResource(1, data1.script, 100));

    // Add registration 2.
    RegistrationData data2;
    data2.registration_id = 101;
    data2.scope = URL(kOrigin, "/bar");
    data2.script = URL(kOrigin, "/script2.js");
    data2.version_id = 201;
    data2.resources_total_size_bytes = 200;
    std::vector<Resource> resources2;
    resources2.push_back(CreateResource(2, data2.script, 200));

    ServiceWorkerDatabase::RegistrationData deleted_version;
    std::vector<int64_t> newly_purgeable_resources;
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data1, resources1, &deleted_version,
            &newly_purgeable_resources));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data2, resources2, &deleted_version,
            &newly_purgeable_resources));

    // Write user data associated with the registration1.
    std::vector<std::string> user_data_out;
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteUserData(data1.registration_id, kOrigin,
            { { "key1", "data1" } }));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteUserData(data1.registration_id, kOrigin,
            { { "key2", "data2" } }));
    ASSERT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadUserData(data1.registration_id, { "key1" }, &user_data_out));
    ASSERT_EQ(1u, user_data_out.size());
    ASSERT_EQ("data1", user_data_out[0]);
    ASSERT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadUserData(data1.registration_id, { "key2" }, &user_data_out));
    ASSERT_EQ(1u, user_data_out.size());
    ASSERT_EQ("data2", user_data_out[0]);

    // Write user data associated with the registration2.
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteUserData(data2.registration_id, kOrigin,
            { { "key3", "data3" } }));
    ASSERT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadUserData(data2.registration_id, { "key3" }, &user_data_out));
    ASSERT_EQ(1u, user_data_out.size());
    ASSERT_EQ("data3", user_data_out[0]);

    // Delete all data associated with the registration1. This shouldn't delete
    // the data associated with registration2.
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->DeleteRegistration(
            data1.registration_id, kOrigin,
            &deleted_version, &newly_purgeable_resources));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadUserData(data1.registration_id, { "key1" }, &user_data_out));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadUserData(data1.registration_id, { "key2" }, &user_data_out));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadUserData(data2.registration_id, { "key3" }, &user_data_out));
    ASSERT_EQ(1u, user_data_out.size());
    EXPECT_EQ("data3", user_data_out[0]);
}

TEST(ServiceWorkerDatabaseTest, UserData_UninitializedDatabase)
{
    std::unique_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory());
    const GURL kOrigin("http://example.com");

    // Should be failed because the database does not exist.
    std::vector<std::string> user_data_out;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadUserData(100, { "key" }, &user_data_out));

    // Should be failed because the associated registration does not exist.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->WriteUserData(100, kOrigin, { { "key", "data" } }));

    // Deleting non-existent entry should succeed.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->DeleteUserData(100, { "key" }));

    // Actually create a new database, but not initialized yet.
    database->LazyOpen(true);

    // Should be failed because the database is not initialized.
    ASSERT_EQ(ServiceWorkerDatabase::UNINITIALIZED, database->state_);
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadUserData(100, { "key" }, &user_data_out));
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->WriteUserData(100, kOrigin, { { "key", "data" } }));

    // Deleting non-existent entry should succeed.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->DeleteUserData(100, { "key" }));
}

TEST(ServiceWorkerDatabaseTest, UpdateVersionToActive)
{
    std::unique_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory());
    GURL origin("http://example.com");

    ServiceWorkerDatabase::RegistrationData deleted_version;
    std::vector<int64_t> newly_purgeable_resources;

    // Should be false because a registration does not exist.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->UpdateVersionToActive(0, origin));

    // Add a registration.
    RegistrationData data;
    data.registration_id = 100;
    data.scope = URL(origin, "/foo");
    data.script = URL(origin, "/script.js");
    data.version_id = 200;
    data.is_active = false;
    data.resources_total_size_bytes = 100;
    std::vector<Resource> resources;
    resources.push_back(CreateResource(1, data.script, 100));
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data, resources, &deleted_version,
            &newly_purgeable_resources));

    // Make sure that the registration is stored.
    RegistrationData data_out;
    std::vector<Resource> resources_out;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->ReadRegistration(
            data.registration_id, origin, &data_out, &resources_out));
    VerifyRegistrationData(data, data_out);
    EXPECT_EQ(1u, resources_out.size());

    // Activate the registration.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->UpdateVersionToActive(data.registration_id, origin));

    // Make sure that the registration is activated.
    resources_out.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->ReadRegistration(
            data.registration_id, origin, &data_out, &resources_out));
    RegistrationData expected_data = data;
    expected_data.is_active = true;
    VerifyRegistrationData(expected_data, data_out);
    EXPECT_EQ(1u, resources_out.size());

    // Delete the registration.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->DeleteRegistration(data.registration_id,
            origin,
            &deleted_version,
            &newly_purgeable_resources));
    EXPECT_EQ(data.registration_id, deleted_version.registration_id);

    // Should be false because the registration is gone.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->UpdateVersionToActive(data.registration_id, origin));
}

TEST(ServiceWorkerDatabaseTest, UpdateLastCheckTime)
{
    std::unique_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory());
    GURL origin("http://example.com");
    ServiceWorkerDatabase::RegistrationData deleted_version;
    std::vector<int64_t> newly_purgeable_resources;

    // Should be false because a registration does not exist.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->UpdateLastCheckTime(0, origin, base::Time::Now()));

    // Add a registration.
    RegistrationData data;
    data.registration_id = 100;
    data.scope = URL(origin, "/foo");
    data.script = URL(origin, "/script.js");
    data.version_id = 200;
    data.last_update_check = base::Time::Now();
    data.resources_total_size_bytes = 100;
    std::vector<Resource> resources;
    resources.push_back(CreateResource(1, data.script, 100));
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data, resources, &deleted_version,
            &newly_purgeable_resources));

    // Make sure that the registration is stored.
    RegistrationData data_out;
    std::vector<Resource> resources_out;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->ReadRegistration(
            data.registration_id, origin, &data_out, &resources_out));
    VerifyRegistrationData(data, data_out);
    EXPECT_EQ(1u, resources_out.size());

    // Update the last check time.
    base::Time updated_time = base::Time::Now();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->UpdateLastCheckTime(
            data.registration_id, origin, updated_time));

    // Make sure that the registration is updated.
    resources_out.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->ReadRegistration(
            data.registration_id, origin, &data_out, &resources_out));
    RegistrationData expected_data = data;
    expected_data.last_update_check = updated_time;
    VerifyRegistrationData(expected_data, data_out);
    EXPECT_EQ(1u, resources_out.size());

    // Delete the registration.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->DeleteRegistration(data.registration_id,
            origin,
            &deleted_version,
            &newly_purgeable_resources));
    EXPECT_EQ(data.registration_id, deleted_version.registration_id);

    // Should be false because the registration is gone.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->UpdateLastCheckTime(
            data.registration_id, origin, base::Time::Now()));
}

TEST(ServiceWorkerDatabaseTest, UncommittedAndPurgeableResourceIds)
{
    std::unique_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory());

    // Write {1, 2, 3} into the uncommitted list.
    std::set<int64_t> ids1;
    ids1.insert(1);
    ids1.insert(2);
    ids1.insert(3);
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteUncommittedResourceIds(ids1));

    std::set<int64_t> ids_out;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetUncommittedResourceIds(&ids_out));
    EXPECT_EQ(ids1, ids_out);

    // Write {2, 4} into the uncommitted list.
    std::set<int64_t> ids2;
    ids2.insert(2);
    ids2.insert(4);
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteUncommittedResourceIds(ids2));

    ids_out.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetUncommittedResourceIds(&ids_out));
    std::set<int64_t> expected = base::STLSetUnion<std::set<int64_t>>(ids1, ids2);
    EXPECT_EQ(expected, ids_out);

    // Move {2, 4} from the uncommitted list to the purgeable list.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->PurgeUncommittedResourceIds(ids2));
    ids_out.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetPurgeableResourceIds(&ids_out));
    EXPECT_EQ(ids2, ids_out);

    // Delete {2, 4} from the purgeable list.
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->ClearPurgeableResourceIds(ids2));
    ids_out.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetPurgeableResourceIds(&ids_out));
    EXPECT_TRUE(ids_out.empty());

    // {1, 3} should be still in the uncommitted list.
    ids_out.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetUncommittedResourceIds(&ids_out));
    expected = base::STLSetDifference<std::set<int64_t>>(ids1, ids2);
    EXPECT_EQ(expected, ids_out);
}

TEST(ServiceWorkerDatabaseTest, DeleteAllDataForOrigin)
{
    std::unique_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory());
    ServiceWorkerDatabase::RegistrationData deleted_version;
    std::vector<int64_t> newly_purgeable_resources;

    // Data associated with |origin1| will be removed.
    GURL origin1("http://example.com");
    GURL origin2("http://example.org");

    // |origin1| has two registrations (registration1 and registration2).
    RegistrationData data1;
    data1.registration_id = 10;
    data1.scope = URL(origin1, "/foo");
    data1.script = URL(origin1, "/resource1");
    data1.version_id = 100;
    data1.resources_total_size_bytes = 2013 + 512;
    data1.foreign_fetch_scopes.push_back(URL(origin1, "/foo/ff"));

    std::vector<Resource> resources1;
    resources1.push_back(CreateResource(1, URL(origin1, "/resource1"), 2013));
    resources1.push_back(CreateResource(2, URL(origin1, "/resource2"), 512));
    ASSERT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(
            data1, resources1, &deleted_version, &newly_purgeable_resources));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteUserData(data1.registration_id, origin1,
            { { "key1", "data1" } }));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteUserData(data1.registration_id, origin1,
            { { "key2", "data2" } }));

    RegistrationData data2;
    data2.registration_id = 11;
    data2.scope = URL(origin1, "/bar");
    data2.script = URL(origin1, "/resource3");
    data2.version_id = 101;
    data2.resources_total_size_bytes = 4 + 5;

    std::vector<Resource> resources2;
    resources2.push_back(CreateResource(3, URL(origin1, "/resource3"), 4));
    resources2.push_back(CreateResource(4, URL(origin1, "/resource4"), 5));
    ASSERT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(
            data2, resources2, &deleted_version, &newly_purgeable_resources));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteUserData(data2.registration_id, origin1,
            { { "key3", "data3" } }));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteUserData(data2.registration_id, origin1,
            { { "key4", "data4" } }));

    // |origin2| has one registration (registration3).
    RegistrationData data3;
    data3.registration_id = 12;
    data3.scope = URL(origin2, "/hoge");
    data3.script = URL(origin2, "/resource5");
    data3.version_id = 102;
    data3.resources_total_size_bytes = 6 + 7;
    data3.foreign_fetch_scopes.push_back(URL(origin2, "/hoge/ff"));

    std::vector<Resource> resources3;
    resources3.push_back(CreateResource(5, URL(origin2, "/resource5"), 6));
    resources3.push_back(CreateResource(6, URL(origin2, "/resource6"), 7));
    ASSERT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(
            data3, resources3, &deleted_version, &newly_purgeable_resources));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteUserData(data3.registration_id, origin2,
            { { "key5", "data5" } }));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteUserData(data3.registration_id, origin2,
            { { "key6", "data6" } }));

    std::set<GURL> origins_to_delete;
    origins_to_delete.insert(origin1);
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->DeleteAllDataForOrigins(origins_to_delete,
            &newly_purgeable_resources));

    // |origin1| should be removed from the unique origin list.
    std::set<GURL> unique_origins;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetOriginsWithRegistrations(&unique_origins));
    EXPECT_EQ(1u, unique_origins.size());
    EXPECT_TRUE(base::ContainsKey(unique_origins, origin2));

    // |origin1| should be removed from the foreign fetch origin list.
    unique_origins.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetOriginsWithForeignFetchRegistrations(&unique_origins));
    EXPECT_EQ(1u, unique_origins.size());
    EXPECT_TRUE(base::ContainsKey(unique_origins, origin2));

    // The registrations for |origin1| should be removed.
    std::vector<RegistrationData> registrations;
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->GetRegistrationsForOrigin(origin1, &registrations, nullptr));
    EXPECT_TRUE(registrations.empty());
    GURL origin_out;
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadRegistrationOrigin(data1.registration_id, &origin_out));

    // The registration for |origin2| should not be removed.
    RegistrationData data_out;
    std::vector<Resource> resources_out;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->ReadRegistration(data3.registration_id, origin2, &data_out, &resources_out));
    VerifyRegistrationData(data3, data_out);
    VerifyResourceRecords(resources3, resources_out);
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadRegistrationOrigin(data3.registration_id, &origin_out));
    EXPECT_EQ(origin2, origin_out);

    // The resources associated with |origin1| should be purgeable.
    std::set<int64_t> purgeable_ids_out;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetPurgeableResourceIds(&purgeable_ids_out));
    EXPECT_EQ(4u, purgeable_ids_out.size());
    EXPECT_TRUE(base::ContainsKey(purgeable_ids_out, 1));
    EXPECT_TRUE(base::ContainsKey(purgeable_ids_out, 2));
    EXPECT_TRUE(base::ContainsKey(purgeable_ids_out, 3));
    EXPECT_TRUE(base::ContainsKey(purgeable_ids_out, 4));

    // The user data associated with |origin1| should be removed.
    std::vector<std::string> user_data_out;
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadUserData(data1.registration_id, { "key1" }, &user_data_out));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadUserData(data1.registration_id, { "key2" }, &user_data_out));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadUserData(data2.registration_id, { "key3" }, &user_data_out));
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_ERROR_NOT_FOUND,
        database->ReadUserData(data2.registration_id, { "key4" }, &user_data_out));

    // The user data associated with |origin2| should not be removed.
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadUserData(data3.registration_id, { "key5" }, &user_data_out));
    ASSERT_EQ(1u, user_data_out.size());
    EXPECT_EQ("data5", user_data_out[0]);
    EXPECT_EQ(
        ServiceWorkerDatabase::STATUS_OK,
        database->ReadUserData(data3.registration_id, { "key6" }, &user_data_out));
    ASSERT_EQ(1u, user_data_out.size());
    EXPECT_EQ("data6", user_data_out[0]);
}

TEST(ServiceWorkerDatabaseTest, DestroyDatabase)
{
    base::ScopedTempDir database_dir;
    ASSERT_TRUE(database_dir.CreateUniqueTempDir());
    std::unique_ptr<ServiceWorkerDatabase> database(
        CreateDatabase(database_dir.GetPath()));

    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->LazyOpen(true));
    ASSERT_TRUE(base::DirectoryExists(database_dir.GetPath()));

    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK, database->DestroyDatabase());
    ASSERT_FALSE(base::DirectoryExists(database_dir.GetPath()));
}

TEST(ServiceWorkerDatabaseTest, GetOriginsWithForeignFetchRegistrations)
{
    std::unique_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory());

    std::set<GURL> origins;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetOriginsWithForeignFetchRegistrations(&origins));
    EXPECT_TRUE(origins.empty());

    ServiceWorkerDatabase::RegistrationData deleted_version;
    std::vector<int64_t> newly_purgeable_resources;

    GURL origin1("http://example.com");
    RegistrationData data1;
    data1.registration_id = 123;
    data1.scope = URL(origin1, "/foo");
    data1.script = URL(origin1, "/script1.js");
    data1.version_id = 456;
    data1.resources_total_size_bytes = 100;
    data1.foreign_fetch_scopes.push_back(URL(origin1, "/foo/bar"));
    std::vector<Resource> resources1;
    resources1.push_back(CreateResource(1, data1.script, 100));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data1, resources1, &deleted_version,
            &newly_purgeable_resources));

    GURL origin2("https://www.example.com");
    RegistrationData data2;
    data2.registration_id = 234;
    data2.scope = URL(origin2, "/bar");
    data2.script = URL(origin2, "/script2.js");
    data2.version_id = 567;
    data2.resources_total_size_bytes = 200;
    std::vector<Resource> resources2;
    resources2.push_back(CreateResource(2, data2.script, 200));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data2, resources2, &deleted_version,
            &newly_purgeable_resources));

    GURL origin3("https://example.org");
    RegistrationData data3;
    data3.registration_id = 345;
    data3.scope = URL(origin3, "/hoge");
    data3.script = URL(origin3, "/script3.js");
    data3.version_id = 678;
    data3.resources_total_size_bytes = 300;
    data3.foreign_fetch_scopes.push_back(URL(origin3, "/hoge/foo"));
    std::vector<Resource> resources3;
    resources3.push_back(CreateResource(3, data3.script, 300));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data3, resources3, &deleted_version,
            &newly_purgeable_resources));

    // |origin3| has three registrations.
    RegistrationData data4;
    data4.registration_id = 456;
    data4.scope = URL(origin3, "/fuga");
    data4.script = URL(origin3, "/script4.js");
    data4.version_id = 789;
    data4.resources_total_size_bytes = 400;
    data4.foreign_fetch_scopes.push_back(URL(origin3, "/fuga/bar"));
    std::vector<Resource> resources4;
    resources4.push_back(CreateResource(4, data4.script, 400));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data4, resources4, &deleted_version,
            &newly_purgeable_resources));

    RegistrationData data5;
    data5.registration_id = 567;
    data5.scope = URL(origin3, "/bla");
    data5.script = URL(origin3, "/script5.js");
    data5.version_id = 890;
    data5.resources_total_size_bytes = 500;
    std::vector<Resource> resources5;
    resources5.push_back(CreateResource(5, data5.script, 500));
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data5, resources5, &deleted_version,
            &newly_purgeable_resources));

    origins.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetOriginsWithForeignFetchRegistrations(&origins));
    EXPECT_EQ(2U, origins.size());
    EXPECT_TRUE(base::ContainsKey(origins, origin1));
    EXPECT_TRUE(base::ContainsKey(origins, origin3));

    // |origin3| has another registration, so should not remove it from the
    // foreign fetch origin list.
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->DeleteRegistration(data4.registration_id, origin3,
            &deleted_version,
            &newly_purgeable_resources));
    EXPECT_EQ(data4.registration_id, deleted_version.registration_id);

    origins.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetOriginsWithForeignFetchRegistrations(&origins));
    EXPECT_EQ(2U, origins.size());
    EXPECT_TRUE(base::ContainsKey(origins, origin1));
    EXPECT_TRUE(base::ContainsKey(origins, origin3));

    // |origin3| should be removed from the foreign fetch origin list, since its
    // only remaining registration doesn't have foreign fetch scopes.
    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->DeleteRegistration(data3.registration_id, origin3,
            &deleted_version,
            &newly_purgeable_resources));
    EXPECT_EQ(data3.registration_id, deleted_version.registration_id);

    origins.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetOriginsWithForeignFetchRegistrations(&origins));
    EXPECT_EQ(1U, origins.size());
    EXPECT_TRUE(base::ContainsKey(origins, origin1));

    // |origin1| should be removed from the foreign fetch origin list, since we
    // replace its registration with one without scopes.
    RegistrationData updated_data1 = data1;
    updated_data1.version_id++;
    updated_data1.resources_total_size_bytes = 12 + 13;
    updated_data1.foreign_fetch_scopes.clear();
    std::vector<Resource> updated_resources1;
    updated_resources1.push_back(
        CreateResource(13, URL(origin1, "/resource3"), 12));
    updated_resources1.push_back(
        CreateResource(14, URL(origin1, "/resource4"), 13));
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(updated_data1, updated_resources1,
            &deleted_version,
            &newly_purgeable_resources));

    origins.clear();
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->GetOriginsWithForeignFetchRegistrations(&origins));
    EXPECT_EQ(0U, origins.size());
}

TEST(ServiceWorkerDatabaseTest, Corruption_NoMainResource)
{
    std::unique_ptr<ServiceWorkerDatabase> database(CreateDatabaseInMemory());
    ServiceWorkerDatabase::RegistrationData deleted_version;
    std::vector<int64_t> newly_purgeable_resources;

    GURL origin("http://example.com");

    RegistrationData data;
    data.registration_id = 10;
    data.scope = URL(origin, "/foo");
    data.script = URL(origin, "/resource1");
    data.version_id = 100;
    data.resources_total_size_bytes = 2016;

    // Simulate that "/resource1" wasn't correctly written in the database by not
    // adding it.
    std::vector<Resource> resources;
    resources.push_back(CreateResource(2, URL(origin, "/resource2"), 2016));

    ASSERT_EQ(ServiceWorkerDatabase::STATUS_OK,
        database->WriteRegistration(data, resources, &deleted_version,
            &newly_purgeable_resources));

    // The database should detect lack of the main resource (i.e. "/resource1").
    RegistrationData data_out;
    std::vector<Resource> resources_out;
    EXPECT_EQ(ServiceWorkerDatabase::STATUS_ERROR_CORRUPTED,
        database->ReadRegistration(data.registration_id, origin, &data_out,
            &resources_out));
    EXPECT_TRUE(resources_out.empty());
}

} // namespace content
